﻿using System;
using System.Collections.Generic;
using Swift;
using SCM;
using Swift.Math;

namespace SCM
{
    /// <summary>
    /// 扩展 Unit 的 AI 行为
    /// </summary>
    public static class UnitAIExt
    {
        // 等待一段时间
        public static StateMachine StartWaiting4Time(this Unit u, Fix64 time, Action callback)
        {
            StateMachine sm = new StateMachine(u.UID);
            sm.NewState("waiting").Run((st, dt) =>
            {
                time -= dt;
                callback.SC();
            }).AsDefault();
            sm.Trans().From("waiting").To("waiting").When(null);
            return sm;
        }

        // 创建对应类型 AI
        public static StateMachine CreateAI(this Unit u)
        {
            StateMachine sm = null;
            switch (u.UnitType)
            {
                case "CommanderCenter":
                case "StarTrack":
                case "CCAcc":
                    sm = u.BuildingCompleted ? ResourceProducer(u) : Constructing(u);
                    break;
                case "Fortress":
                    sm = u.BuildingCompleted ? Fortress(u) : Constructing(u);
                    break;
                case "Barrack":
                case "BrkAcc":
                case "Factory":
                case "FctAcc":
                case "Airport":
                case "ApAcc":
                    sm = u.BuildingCompleted ? BattleUnitProducer(u) : Constructing(u);
                    break;
                case "Bunker":
                case "BigBunker":
                    sm = u.BuildingCompleted ? HoldAndAttack(u) : Constructing(u);
                    break;
                case "Soldier":
                case "Firebat":
                case "Ghost":
                case "Tank":
                case "Robot":
                case "Banshee":
                case "Viking":
                    sm = MoveAndAttack(u);
                    break;
                case "SiegeTank":
                    sm = HoldAndAttack(u);
                    break;
                case "RadarSign":
                case "TempVisionAtBeginning":
                case "TempVision":
                    sm = Suicide(u);
                    break;
                case "SoldierCarrier":
                    sm = SoldierCarrier(u);
                    break;
                case "HealingTower":
                    sm = HealingTower(u);
                    break;
                case "TreasureBox":
                    sm = TreasureBox(u);
                    break;
                case "TreasureBoxCarrier":
                    sm = TreasureBoxCarrier(u);
                    break;
            }

            return sm;
        }

        // 建造中的建筑物
        static StateMachine Constructing(Unit u)
        {
            var sm = new StateMachine(u.UID);

            var waitingTime = u.cfg.ConstructingTime;
            var hpAddPerSec = (u.cfg.MaxHp - 1) / waitingTime;
            sm.NewState("constructing").Run((st, dt) =>
            {
                waitingTime -= dt;
                u.Hp += hpAddPerSec * dt;
            }).AsDefault();
            sm.NewState("complete").OnRunIn((st) =>
            {
                u.BuildingComplete();
            }).Run(null);

            sm.Trans().From("constructing").To("complete").When((st) => u.BuildingCompleted || waitingTime <= 0);

            return sm;
        }

        // 资源生产，但一段时间后自毁
        static StateMachine ResourceProducerWithSuicide(Unit u)
        {
            var sm = new StateMachine(u.UID);
            var rTypes = u.cfg.GenResourceType;
            var rates = u.cfg.GenResourceRate;
            Fix64 waitingTime = 0f;

            // 资源生产间隔时间为 1 秒
            sm.NewState("waiting").Run((st, dt) =>
            {
                waitingTime += dt;
            }).AsDefault();

            // 每秒产生指定数量的资源
            sm.NewState("addResource").Run((st, dt) =>
            {
                FC.For(rTypes.Length, (i) =>
                {
                    var t = rTypes[i];
                    var r = rates[i];
                    u.Room.AddResource(u.Player, t, r);
                });

                waitingTime -= 1;
                u.Hp -= 1;
            });

            // 自毁
            sm.NewState("dead").Run(null);

            sm.Trans().From("waiting").To("addResource").When((st) => waitingTime >= 1);
            sm.Trans().From("addResource").To("waiting").When((st) => waitingTime < 1);
            sm.Trans().From("waiting|addResource").To("dead").When((st) => u.Hp <= 0);

            return sm;
        }

        // 要塞：生产资源的同时搜索攻击目标
        static StateMachine Fortress(Unit u)
        {
            var sm = new StateMachine(u.UID);
            var rTypes = u.cfg.GenResourceType;
            var rates = u.cfg.GenResourceRate;

            Unit target = null;
            Fix64 resourceWatingTime = 0;
            Fix64 attackWaitngTime = 0;

            // 生产资源并搜索目标
            sm.NewState("guarding").Run((st, te) =>
            {
                resourceWatingTime += te;
                attackWaitngTime += te;

                // 生产资源
                if (resourceWatingTime >= 1)
                {
                    resourceWatingTime -= 1;
                    FC.For(rTypes.Length, (i) =>
                    {
                        u.Room.AddResource(u.Player, rTypes[i], rates[i]);
                    });
                }

                // 搜索攻击目标
                target = u.GetFirstAttackableUnitsInRange(u.cfg.AttackRange);
            }).AsDefault();

            // 攻击目标
            sm.NewState("attacking").Run((st, te) =>
            {
                u.Room.DoAttack(u, target);
                attackWaitngTime = 0;
            });

            // 状态迁移
            sm.Trans().From("guarding").To("attacking").When((st) => target != null && target.Hp > 0 && attackWaitngTime >= u.cfg.AttackInterval);
            sm.Trans().From("attacking").To("guarding").When((st) => target == null || target.Hp <= 0 || attackWaitngTime < u.cfg.AttackInterval);

            return sm;
        }

        // 资源生产
        static StateMachine ResourceProducer(Unit u) // , Dictionary<string, int> initialCount = null)
        {
            var sm = new StateMachine(u.UID);
            var rTypes = u.cfg.GenResourceType;
            var rates = u.cfg.GenResourceRate;
            Fix64 waitingTime = 0f;

            // 生成初始资源量
            //bool initialized = false;
            //sm.NewState("initialResource").Run((st, dt) =>
            //{
            //    FC.For(rTypes.Length, (i) =>
            //    {
            //        var t = rTypes[i];
            //        var initValue = initialCount == null || !initialCount.ContainsKey(t) ? 0 : initialCount[t];
            //        u.Room.AddResource(u.Player, t, initValue);
            //    });
            //    initialized = true;
            //}).AsDefault();

            // 资源生产间隔时间为 1 秒
            sm.NewState("waiting").Run((st, dt) =>
            {
                waitingTime += dt;
            }).AsDefault();

            // 每秒产生指定数量的资源
            sm.NewState("addResource").Run((st, dt) =>
            {
                FC.For(rTypes.Length, (i) =>
                {
                    var t = rTypes[i];
                    var r = rates[i];

                    // 生产的钱，从矿点消耗掉
                    if (t == "Money")
                    {
                        var stubArr = u.Room.GetUnitsByType("CCStub", 0);
                        var stubPos = u.Owner == null ? u.Pos : u.Owner.Pos;
                        Unit stub = null;
                        foreach (var s in stubArr)
                        {
                            if (s.Pos == stubPos)
                            {
                                stub = s;
                                break;
                            }
                        }

                        // 找不到矿点就是矿采干了
                        if (stub != null)
                        {
                            r = stub.Hp < r ? stub.Hp : r;
                            stub.Hp -= r;
                        }
                        else
                            r = 0;
                    }

                    u.Room.AddResource(u.Player, t, r);
                    u.Room.OnProduceResource(u, t, r);
                });

                waitingTime -= 1;
            });

            // sm.Trans().From("initialResource").To("waiting").When((st) => initialized);
            sm.Trans().From("waiting").To("addResource").When((st) => waitingTime >= 1);
            sm.Trans().From("addResource").To("waiting").When((st) => waitingTime < 1);

            return sm;
        }

        // 生产建筑
        static StateMachine BattleUnitProducer(Unit u)
        {
            var sm = new StateMachine(u.UID);
            var genUnitType = "";

            // 尝试将建造列表的内容推给挂件执行
            Action try2Push2Accessories = () =>
            {
                if (u.UnitCosntructingWaitingList.Count <= 1)
                    return;
                
                foreach (var acc in u.Accessories)
                {
                    if (acc == null
                        || !acc.BuildingCompleted
                        || acc.Room.GetSM(acc).CurrentState != "idle")
                        continue;

                    var genType = u.UnitCosntructingWaitingList[0];
                    u.UnitCosntructingWaitingList.RemoveAt(0);
                    acc.UnitCosntructingWaitingList.Add(genType);
                    u.Room.NotifyConstructingWaitingListChanged(u, genType);
                    return;
                }
            };

            Fix64 waitingTime = 0f;
            sm.NewState("idle").Run(null).AsDefault();
            sm.NewState("constructingBattleUnit")
                .OnRunIn((st) =>
                {
                    genUnitType = u.UnitCosntructingWaitingList[0];
                    waitingTime = u.Room.GetPlayerUnitConfig(u.Player, genUnitType).ConstructingTime;
                    u.Room.OnConstructingBattleUnitStarted(u, genUnitType);
                    u.Room.NotifyConstructingWaitingListChanged(u, genUnitType);
                })
                .Run((st, dt) =>
                {
                    waitingTime -= dt;
                    if (waitingTime <= 0)
                    {
                        //if (u.Room.OnBattleUnitConstructingCompleted(u, genUnitType) == null)
                        //    waitingTime += dt; // 可能因为人口上限卡住产兵，则一直等在最后一刻
                        //else
                        {
                            genUnitType = u.UnitCosntructingWaitingList[0];
                            u.UnitCosntructingWaitingList.RemoveAt(0); // 从建造列表移除
                            u.UnitConstructed.Add(genUnitType);
                            u.Room.NotifyConstructingWaitingListChanged(u, genUnitType);
                        }
                    }

                    try2Push2Accessories();
                });

            sm.Trans().From("idle").To("constructingBattleUnit").When((st) => u.UnitCosntructingWaitingList.Count > 0);
            sm.Trans().From("constructingBattleUnit").To("idle").When((st) => waitingTime <= 0);

            return sm;
        }

        // 简单地面移动
        static StateMachine Move(Unit u)
        {
            var sm = new StateMachine(u.UID);

            sm.NewState("idle").OnRunIn((st) =>
            {
                u.MovePath.Clear();
                u.PreferredVelocity = Vec2.Zero;
            }).Run(null).AsDefault();
            sm.NewState("moving").Run(MakeMove(u, u.MovePath));

            sm.Trans().From("idle").To("moving").When((st) => u.MovePath.Count > 0);
            sm.Trans().From("moving").To("idle").When((st) => u.MovePath.Count == 0);

            return sm;
        }

        // 地面固定位置攻击，不可移动
        static StateMachine HoldAndAttack(Unit u)
        {
            var sm = new StateMachine(u.UID);

            // 攻击目标
            Unit target = null;

            // 原地寻找攻击目标
            sm.NewState("guarding").OnRunIn((st) =>
            {
                u.PreferredVelocity = Vec2.Zero;
            }).Run((st, dt) =>
            {
                target = u.GetFirstAttackableUnitsInRange(u.cfg.AttackRange);
            }).AsDefault();

            // 站立攻击目标
            var attackImpl = MakeHoldAndAttack(u, () => target);
            sm.NewState("attacking").Run((st, te) =>
            {
                attackImpl(st, te);
                target = u.GetFirstAttackableUnitsInRange(u.cfg.AttackRange); // 重新搜索目标
            });

            sm.Trans().From("guarding").To("attacking").When((st) => u.CanAttack(target));
            sm.Trans().From("attacking").To("guarding").When((st) => !u.CanAttack(target));

            return sm;
        }

        // 地面移动和攻击
        static StateMachine MoveAndAttack(Unit u)
        {
            var sm = new StateMachine(u.UID);

            // 攻击目标
            Unit target = null;
            Func<Fix64, Unit> confirmTarget = (dt) =>
            {
                if (u.IgnoreTargetWhenMoving && u.MovePath.Count > 0)
                    return null;

                // 原有攻击目标还可以攻击，并且是优先攻击类型（可以攻击自己的是优先攻击类型），就不更换目标
                if (u.CanAttack(target) && target.CanAttack(u) && u.InAttackRange(target))
                    return target;
                else
                    return u.GetFirstAttackableUnitsInRange(u.cfg.GuardingRange);
            };

            // 原地寻找攻击目标
            sm.NewState("guarding").OnRunIn((st) =>
            {
                u.PreferredVelocity = Vec2.Zero;
            }).Run((st, dt) =>
            {
                target = confirmTarget(dt);
            }).AsDefault();

            // 沿路径移动
            var moveImpl = MakeMove(u, u.MovePath);
            sm.NewState("moving").Run((st, dt) =>
            {
                moveImpl(st, dt);
                target = confirmTarget(dt);
            });

            // 向目标直线移动
            var msImpl = MakeStraightMove(u, () => target.Pos);
            sm.NewState("chasing").Run((st, dt) =>
            {
                msImpl(st, dt);
                target = confirmTarget(dt);
            });

            // 站立攻击目标
            var attackingImpl = MakeHoldAndAttack(u, () => target);
            sm.NewState("attacking").OnRunIn((st) => u.PreferredVelocity = Vec2.Zero)
                .Run((st, dt) =>
                {
                    attackingImpl(st, dt);
                    target = confirmTarget(dt);
                });

            sm.Trans().From("guarding").To("moving").When((st) => u.MovePath.Count > 0);
            sm.Trans().From("guarding").To("chasing").When((st) => u.CanAttack(target) && !u.InAttackRange(target));
            sm.Trans().From("guarding").To("attacking").When((st) => u.CanAttack(target) && u.InAttackRange(target));

            sm.Trans().From("moving").To("guarding").When((st) => u.MovePath.Count == 0);
            sm.Trans().From("moving").To("chasing").When((st) => u.CanAttack(target));

            sm.Trans().From("chasing").To("guarding").When((st) => !u.CanAttack(target));
            sm.Trans().From("chasing").To("attacking").When((st) => u.CanAttack(target) && u.InAttackRange(target));

            sm.Trans().From("attacking").To("chasing").When((st) => u.CanAttack(target) && !u.InAttackRange(target));
            sm.Trans().From("attacking").To("guarding").When((st) => !u.CanAttack(target));

            return sm;
        }

        // 存活一段时间后死亡
        static StateMachine Suicide(Unit u)
        {
            var sm = new StateMachine(u.UID);
            sm.NewState("countingdown").Run((st, dt) => { u.Hp -= dt; }).AsDefault();
            sm.Trans().From("countingdown").To("countingdown").When((st) => false);
            return sm;
        }

        // 伞兵投放机
        static StateMachine SoldierCarrier(Unit u)
        {
            var sm = new StateMachine(u.UID);

            var dropped = false;
            var moveImpl = MakeMove(u, u.MovePath);
            sm.NewState("moving").Run((st, te) =>
            {
                moveImpl(st, te);
                u.Pos += u.PreferredVelocity;
                u.Dir = u.PreferredVelocity.Dir();

                if (!dropped && u.MovePath.Count < 2)
                {
                    dropped = true;
                    foreach (var gu in u.UnitCosntructingWaitingList)
                        u.Room.AddNewUnit(null, gu, u.Pos, u.Player);
                }
            }).AsDefault();
            sm.NewState("dead").OnRunIn((st) =>
            {
                u.Hp = 0;
            }).Run(null);

            // 到目的地就销毁
            sm.Trans().From("moving").To("dead").When((st) => u.MovePath.Count == 0);
            return sm;
        }

        // 治疗塔，固定间隔给范围内的己方单位加血，按百分比
        static StateMachine HealingTower(Unit u)
        {
            var sm = new StateMachine(u.UID);

            var t = Fix64.Zero;
            sm.NewState("waiting").Run((st, dt) =>
            {
                t += dt;
            }).AsDefault();
            sm.NewState("healing").Run((st, dt) =>
            {
                t = 0;
                u.Room.DoAttack(u, u);
            });

            sm.Trans().From("waiting").To("healing").When((st) => t >= u.cfg.AttackInterval);
            sm.Trans().From("healing").To("waiting").When((st) => t <= 0);

            return sm;
        }

        #region 基本行为实现

        // 沿路径移动，返回下一个目标路径点
        static Vec2 RunPath(Vec2 from, List<Vec2> path, Fix64 dist)
        {
            var dst = path.Count > 0 ? path[path.Count - 1] : from;
            var f = from;

            while (dist > 0 && path.Count > 0)
            {
                var pt = path[0];
                var d = (pt - f).Length;
                if (dist >= d)
                {
                    dist -= d;
                    f = pt;
                    path.RemoveAt(0);
                }
                else
                    return pt;
            }

            return dst;
        }

        // 沿路径移动
        static Action<State, Fix64> MakeMove(Unit u, List<Vec2> path)
        {
            return (st, dt) =>
            {
                var v = u.cfg.MaxVelocity;
                var maxDist = v * dt;
                var dst = RunPath(u.Pos, path, maxDist);
                var dir = dst - u.Pos;
                var dist = dir.Length;
                u.PreferredVelocity = (maxDist >= dir.Length) ? dir : dir * maxDist / dist;
            };
        }

        // 直线向目标移动
        static Action<State, Fix64> MakeStraightMove(Unit u, Func<Vec2> getDst)
        {
            return (st, dt) =>
            {
                var dst = getDst();
                var v = u.cfg.MaxVelocity;
                var maxDist = v * dt;
                var dir = dst - u.Pos;
                var dist = dir.Length;
                u.PreferredVelocity = (maxDist >= dir.Length) ? dir : dir * maxDist / dist;
            };
        }

        // 站立攻击目标
        static Action<State, Fix64> MakeHoldAndAttack(Unit u, Func<Unit> getTarget)
        {
            Fix64 attackingInterval = 0f;
            return (st, dt) =>
            {
                if (attackingInterval > 0)
                    attackingInterval -= dt;
                else
                {
                    var target = getTarget();
                    if (target == null)
                        return;

                    u.Room.DoAttack(u, target);
                    attackingInterval = u.cfg.AttackInterval;
                }
            };
        }

        // 宝箱等人捡
        static StateMachine TreasureBox(Unit u)
        {
            var sm = new StateMachine(u.UID);

            Unit triggerUnit = null;

            sm.NewState("guarding").Run((st, te) =>
            {
                // 搜索附近可能触发宝箱的单位
                var ts = u.Room.GetUnitsInCircleArea(u.Pos, u.cfg.ShowSize, (tar) =>
                {
                    // 可移动的地面非中立单位
                    return !tar.cfg.IsAirForce
                            && !tar.IsNeutral
                            && tar.cfg.MaxVelocity > 0;
                });

                if (ts.Length > 0)
                    triggerUnit = ts[0];
            }).AsDefault();

            sm.NewState("trigger").OnRunIn((st) =>
            {
                u.Room.TBRunner.TriggerOne(triggerUnit, u.UID);
                u.Hp = 0;
            }).Run(null);

            sm.Trans().From("guarding").To("trigger").When((st) => triggerUnit != null);

            return sm;
        }

        // 宝箱投放机
        static StateMachine TreasureBoxCarrier(Unit u)
        {
            var sm = new StateMachine(u.UID);

            var dropped = false;
            var moveImpl = MakeMove(u, u.MovePath);
            sm.NewState("moving").Run((st, te) =>
            {
                moveImpl(st, te);
                u.Pos += u.PreferredVelocity;
                u.Dir = u.PreferredVelocity.Dir();

                if (!dropped && u.MovePath.Count < 2)
                {
                    dropped = true;
                    u.Room.TBRunner.CreateRandomTreasureBox(u.Pos);
                }
            }).AsDefault();
            sm.NewState("dead").OnRunIn((st) =>
            {
                u.Hp = 0;
            }).Run(null);

            // 到目的地就销毁
            sm.Trans().From("moving").To("dead").When((st) => u.MovePath.Count == 0);

            return sm;
        }

        #endregion
    }
}
